package com.zz.framework.core.utils.sandbox

import android.content.ContentUris
import android.content.ContentValues
import android.database.Cursor
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.provider.OpenableColumns
import com.zz.framework.core.utils.AppGlobals
import com.zz.framework.core.utils.file.sandbox.DirectoryType
import com.zz.framework.core.utils.file.sandbox.FileType
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream

/**
 * Copyright © 2024 zhun All rights reserved.
 * Created by Zha0Zhun on 2024/6/22 09:23.
 * @author: Zha0Zhun 1820022519@qq.com
 * @version: V1.0
 */
internal class FileMediaStoreImpl : IFile {
    companion object {
        val context = AppGlobals.getApplication()
        private val mInstance by lazy { FileMediaStoreImpl() }
        fun getInstance(): IFile {
            return mInstance
        }
    }

    override fun newFile(displayName: String, content: ByteArray, onSuccess: (Uri) -> Unit, onError: (Exception) -> Unit) {
        val fileType = FileType.fromFileName(displayName)
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
            put(MediaStore.MediaColumns.MIME_TYPE, fileType.mimeType)
            put(MediaStore.MediaColumns.RELATIVE_PATH, fileType.directory)
        }
        val uri: Uri = when (fileType.directory) {
            Environment.DIRECTORY_PICTURES -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
            Environment.DIRECTORY_MOVIES -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
            Environment.DIRECTORY_MUSIC -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
            else -> MediaStore.Files.getContentUri("external")
        }
        val insertUri: Uri? = AppGlobals.getApplication().contentResolver.insert(uri, contentValues)
        try {
            if (insertUri != null) {
                AppGlobals.getApplication().contentResolver.openOutputStream(insertUri)?.use { outputStream ->
                    outputStream.write(content)
                }
                onSuccess(insertUri)
            } else {
                onError(IOException("Failed to create new MediaStore record."))
            }
        } catch (e: Exception) {
            onError(e)
        }
    }

    override fun newFile(displayName: String, inputStream: InputStream, onSuccess: (Uri) -> Unit, onError: (Exception) -> Unit) {
        val fileType = FileType.fromFileName(displayName)
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
            put(MediaStore.MediaColumns.MIME_TYPE, fileType.mimeType)
            put(MediaStore.MediaColumns.RELATIVE_PATH, fileType.directory)
        }
        try {
            val uri: Uri = when (fileType.directory) {
                Environment.DIRECTORY_PICTURES -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                Environment.DIRECTORY_MOVIES -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
                Environment.DIRECTORY_MUSIC -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
                else -> MediaStore.Files.getContentUri("external")
            }
            val insertUri: Uri? = AppGlobals.getApplication().contentResolver.insert(uri, contentValues)
            if (insertUri != null) {
                AppGlobals.getApplication().contentResolver.openOutputStream(insertUri)?.use { outputStream ->
                    inputStream.copyTo(outputStream)
                }
                onSuccess(insertUri)
            } else {
                onError(IOException("Failed to create new MediaStore record."))
            }
        } catch (e: Exception) {
            onError(e)
        }
    }

    override fun deleteFile(uri: Uri, onSuccess: () -> Unit, onError: (Exception) -> Unit) {
        try {
            val rowsDeleted = AppGlobals.getApplication().contentResolver.delete(uri, null, null)
            if (rowsDeleted > 0) {
                onSuccess()
            } else {
                onError(IOException("Failed to delete file."))
            }
        } catch (e: Exception) {
            onError(e)
        }
    }

    override fun deleteFile(displayName: String, onSuccess: () -> Unit, onError: (Exception) -> Unit) {
        val selection = "${MediaStore.MediaColumns.DISPLAY_NAME}=?"
        val selectionArgs = arrayOf(displayName)
        val uri = getContentUriByDisplayName(displayName)
        AppGlobals.getApplication().contentResolver.query(uri, null, selection, selectionArgs, null)?.use { cursor ->
            if (cursor.moveToFirst()) {
                val uriIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
                val id = cursor.getLong(uriIndex)
                val contentUri = ContentUris.withAppendedId(uri, id)
                try {
                    val rowsDeleted = AppGlobals.getApplication().contentResolver.delete(contentUri, null, null)
                    if (rowsDeleted > 0) {
                        onSuccess()
                    } else {
                        onError(IOException("Failed to read file content."))
                    }
                } catch (e: Exception) {
                    onError(e)
                }
            } else {
                onError(IOException("File not found."))
            }
        } ?: onError(IOException("Failed to query MediaStore."))
    }

    override fun readFile(uri: Uri, onSuccess: (ByteArray) -> Unit, onError: (Exception) -> Unit) {
        try {
            val content = context.contentResolver.openInputStream(uri)?.buffered()?.use { inputStream ->
                inputStream.readBytes()
            }
            if (content != null) {
                onSuccess(content)
            } else {
                onError(IOException("Failed to read file content."))
            }
        } catch (e: Exception) {
            onError(e)
        }
    }

    override fun readFile(displayName: String, onSuccess: (ByteArray) -> Unit, onError: (Exception) -> Unit) {
        val selection = "${MediaStore.MediaColumns.DISPLAY_NAME}=?"
        val selectionArgs = arrayOf(displayName)
        val uri: Uri = getContentUriByDisplayName(displayName)
        AppGlobals.getApplication().contentResolver.query(uri, null, selection, selectionArgs, null)?.use { cursor ->
            if (cursor.moveToFirst()) {
                val uriIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
                val id = cursor.getLong(uriIndex)
                val contentUri = ContentUris.withAppendedId(uri, id)
                try {
                    val content = AppGlobals.getApplication().contentResolver.openInputStream(contentUri)?.buffered()?.use { inputStream ->
                        inputStream.readBytes()
                    }
                    if (content != null) {
                        onSuccess(content)
                    } else {
                        onError(IOException("Failed to read file content."))
                    }
                } catch (e: Exception) {
                    onError(e)
                }
            } else {
                onError(IOException("File not found."))
            }
        } ?: onError(IOException("Failed to query MediaStore."))
    }

    override fun queryFileByName(displayName: String, onSuccess: (Uri) -> Unit, onError: (Exception) -> Unit) {
        val selection = "${MediaStore.MediaColumns.DISPLAY_NAME}=?"
        val selectionArgs = arrayOf(displayName)
        val uri: Uri = getContentUriByDisplayName(displayName)
        AppGlobals.getApplication().contentResolver.query(uri, null, selection, selectionArgs, null)?.use { cursor ->
            if (cursor.moveToFirst()) {
                val uriIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
                val id = cursor.getLong(uriIndex)
                val contentUri = ContentUris.withAppendedId(uri, id)
                onSuccess(contentUri)
            } else {
                onError(IOException("File not found."))
            }
        } ?: onError(IOException("Failed to query MediaStore."))
    }

    private fun getContentUriByDisplayName(displayName: String): Uri {
        val fileType = FileType.fromFileName(displayName)
        val uri: Uri = when (fileType.directory) {
            Environment.DIRECTORY_PICTURES -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
            Environment.DIRECTORY_MOVIES -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
            Environment.DIRECTORY_MUSIC -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
            else -> MediaStore.Files.getContentUri("external")
        }
        return uri
    }

    override fun updateFile(uri: Uri, newContent: ByteArray, onSuccess: () -> Unit, onError: (Exception) -> Unit) {
        try {
            AppGlobals.getApplication().contentResolver.openOutputStream(uri)?.use { outputStream ->
                outputStream.write(newContent)
                onSuccess()
            } ?: onError(IOException("Failed to open output stream."))
        } catch (e: Exception) {
            onError(e)
        }
    }

    override fun updateFile(displayName: String, newContent: ByteArray, onSuccess: () -> Unit, onError: (Exception) -> Unit) {
        val fileType = FileType.fromFileName(displayName)
        val selection = "${MediaStore.MediaColumns.DISPLAY_NAME}=?"
        val selectionArgs = arrayOf(displayName)
        val uri: Uri = getContentUriByDisplayName(displayName)
        AppGlobals.getApplication().contentResolver.query(uri, null, selection, selectionArgs, null)?.use { cursor ->
            if (cursor.moveToFirst()) {
                val uriIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
                val id = cursor.getLong(uriIndex)
                val contentUri = ContentUris.withAppendedId(uri, id)
                try {
                    AppGlobals.getApplication().contentResolver.openOutputStream(contentUri)?.use { outputStream ->
                        outputStream.write(newContent)
                        onSuccess()
                    } ?: onError(IOException("Failed to open output stream."))
                } catch (e: Exception) {
                    onError(e)
                }
            } else {
                onError(IOException("File not found."))
            }
        } ?: onError(IOException("Failed to query MediaStore."))
    }

    override fun renameFile(uri: Uri, newDisplayName: String, onSuccess: (Uri) -> Unit, onError: (Exception) -> Unit) {
        val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, newDisplayName) }
        try {
            val rowsUpdated = context.contentResolver.update(uri, contentValues, null, null)
            if (rowsUpdated > 0) {
                onSuccess(uri)
            } else {
                onError(IOException("Failed to rename file."))
            }
        } catch (e: Exception) {
            onError(e)
        }
    }

    override fun renameFile(displayName: String, newDisplayName: String, onSuccess: (Uri) -> Unit, onError: (Exception) -> Unit) {
        queryFileByName(displayName, { contentUri ->
            renameFile(contentUri, newDisplayName, onSuccess, onError)
        }, onError)
    }

    override fun writeInputStreamToAlbum(inputStream: InputStream, displayName: String, onSuccess: (Uri) -> Unit, onError: (Exception) -> Unit) {
        val values = ContentValues()
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        values.put(MediaStore.MediaColumns.MIME_TYPE, FileType.fromFileName(displayName).mimeType)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
        } else {
            values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/$displayName")
        }
        try {
            BufferedInputStream(inputStream)?.use { bis ->
                val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
                if (uri != null) {
                    context.contentResolver.openOutputStream(uri)?.use { outputStream ->
                        val bos = BufferedOutputStream(outputStream)
                        val buffer = ByteArray(1024)
                        var bytes = bis.read(buffer)
                        while (bytes >= 0) {
                            bos.write(buffer, 0, bytes)
                            bos.flush()
                            bytes = bis.read(buffer)
                        }
                    }
                    onSuccess(uri)
                } else {
                    onError(IOException("Failed to create new file in MediaStore."))
                }
            }
        } catch (e: Exception) {
            onError(e)
        }
    }

    override fun addBitmapToAlbum(displayName: String, bitmap: Bitmap, onSuccess: (Uri) -> Unit, onError: (Exception) -> Unit) {
        val fileType = FileType.fromFileName(displayName)
        val content = bitmapToByteArray(bitmap, fileType)
        newFile(displayName, content, onSuccess, onError)
    }

    override fun mkdirs(displayName: String, directoryType: DirectoryType, onSuccess: (Uri) -> Unit, onError: (Exception) -> Unit) {
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
            put(MediaStore.MediaColumns.MIME_TYPE, "vnd.android.document/directory")
            put(MediaStore.MediaColumns.RELATIVE_PATH, directoryType.directory)
        }
        val uri: Uri = when (directoryType.directory) {
            Environment.DIRECTORY_PICTURES -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
            Environment.DIRECTORY_MOVIES -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
            Environment.DIRECTORY_MUSIC -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
            else -> MediaStore.Files.getContentUri("external")
        }
        val insertUri: Uri? = AppGlobals.getApplication().contentResolver.insert(uri, contentValues)
        try {
            if (insertUri != null) {
                onSuccess(insertUri)
            } else {
                onError(IOException("Failed to create new MediaStore record."))
            }
        } catch (e: Exception) {
            onError(e)
        }
    }

    override fun getFileName(uri: Uri): String? {
        var fileName: String? = null
        val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
        cursor?.use {
            if (it.moveToFirst()) {
                val columnIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                if (columnIndex != -1) {
                    fileName = it.getString(columnIndex)
                }
            }
        }
        return fileName
    }

    private fun bitmapToByteArray(bitmap: Bitmap, fileType: FileType): ByteArray {
        val format = when (fileType) {
            FileType.IMAGE_PNG -> Bitmap.CompressFormat.PNG
            else -> Bitmap.CompressFormat.JPEG
        }
        return ByteArrayOutputStream().apply {
            bitmap.compress(format, 100, this)
        }.toByteArray()
    }
}